從昨天的例子,可以看出要使用一個樣板引擎大致上會有怎樣的操作。接下來的工作,就是包裝一下樣板引擎,這樣就可以在需要時抽換。
不過程式碼本身也還需要進一步整理,抽出共用的參數以及共用的bootstrap過程。
先來找出一些共用的參數,通常至少會有資料庫連接的參數,之後再視需要加入更多的參數。不過為了讓其他程式不用require太多東西,我們只require一支bootstrap.php,載入參數以及初始話的過程,就都在bootstrap中完成。
先來看一下之前論壇的首頁程式,然後再看看怎樣調整。
<?php
session_start();
include 'vendor/autoload.php';
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
$conn = mysql_connect('localhost', 'root', '');
if(!$conn)
die('mysql connection error.');
mysql_select_db('myforum');
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = "#EEFFEE";
} else {
$style = "#FFFFFF";
}
$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
$result1 = mysql_query($sql, $conn);
$row1 = mysql_fetch_array($result1);
$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
$count++;
}
#include 'views/index.php';
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem('views');
$twig = new Twig_Environment($loader, array('cache'=>'cache'));
$template = $twig->loadTemplate('index.html');
echo $template->render(array(
'member'=>$member,
'data'=>$data,
'name'=>$name,
'message'=>$message
));
?>
因為頁面都移出去了,所以程式就沒那麼長,而且容易修改,這也是要做程式與頁面分離的原因。由於後續會再加入更種形式的檔案,並且建立簡單的view class,所以還是先做基本的目錄規劃。目前已經有的目錄:
為了後續管理方便,先訂兩個目錄:
然後把一些目前可以想到的共用參數,拉出來,放到config.php
<?php
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASSWORD', '');
define('DB_NAME', 'myforum');
define('CLASS_DIR', 'class/');
把一些初始化的動作,放進bootstrap.php。這個檔除了會引入config.php之外,還會載入autoloader,並且預先連接資料庫。
接下來是bootstrap.php:
<?php
session_start();
include 'config.php';
//register autoloader
include 'vendor/autoload.php';
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
spl_autoload_extensions(".php");
spl_autoload_register();
spl_autoload_register(function($class) {
$class = str_replace('_', '/', $class);
$class = str_replace('\\', '/', $class);
include $class.".php";
}, false);
//connect db
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$conn)
die('mysql connection error.'.mysql_error());
mysql_select_db(DB_NAME);
最後index.php就只要引用bootstrap.php就可以:
<?php
require 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = "#EEFFEE";
} else {
$style = "#FFFFFF";
}
$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
$result1 = mysql_query($sql, $conn);
$row1 = mysql_fetch_array($result1);
$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
$count++;
}
#include 'views/index.php';
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem('views');
$twig = new Twig_Environment($loader, array('cache'=>'cache'));
$template = $twig->loadTemplate('index.html');
echo $template->render(array(
'member'=>$member,
'data'=>$data,
'name'=>$name,
'message'=>$message
));
整理完以後,先確定程式會動,然後再來進行下一步。這一步是把樣板引擎需要的動作進一步抽象化,寫出一個View類別,之後具體的樣板引擎可以抽換,而不需要修改主程式。
還是先來觀察一下使用樣板引擎時,必要的動作。其實歸納一下就知道,除了樣板引擎自身的設定,大體上還需要用幾個步驟來操作:
另外,為了要能使用參數來抽換樣板引擎,直接在View類別寫一個工廠方法,讓他回傳實作的類別。為了管理方便,在開設計View時,也把namespace加進去了。首先來看一個簡單的View實作:
<?php
namespace Fillano\Core;
abstract class View
{
public abstract function asign($datas = array());
public abstract function render($template="", $ext);
public abstract function fetch($template="", $ext);
public static function getInstance($path) {
if (!defined('TEMPLATE_ENGINE')) {
die('Please specify a template engine in config.php');
}
$class = "Fillano\\Core\\".TEMPLATE_ENGINE;
return new $class($path);
}
}
要使用這個類別,還需要在設定檔中指定實作類別的名稱。其實這樣的依賴關係是比較差的設計,不過先讓他會動就好,反正還是有達到可抽換實作的目的。更好(?)的作法,可能要使用IoC/DI容器就是了...這個比較見仁見智
然後實作出一個TwigView類別,裡面包了Twig,用assign/render/fetch來操作:
<?php
namespace Fillano\Core;
class TwigView
{
private $engine;
private $datas;
public function __construct($path)
{
$this->path = $path;
\Twig_Autoloader::register();
$loader = new \Twig_Loader_Filesystem($path);
$this->engine = new \Twig_Environment($loader, array('cache'=>'cache'));
}
public function assign($datas) {
$this->datas = $datas;
}
public function render($template, $ext) {
echo $this->fetch($template, $ext);
}
public function fetch($template, $ext) {
$file = implode('/', explode('_', $template)).'.'.$ext;
$twig = $this->engine->loadTemplate($file);
return $twig->render($this->datas);
}
}
為了讓工廠方法找到實作類別,所以在config.php再加上一個設定:
<?php
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASSWORD', '');
define('DB_NAME', 'myforum');
define('CLASS_DIR', 'class/');
define('TEMPLATE_ENGINE', 'TwigView');
最後,把index.php中的程式碼改掉:
<?php
require 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
$member = true;
$name = $_SESSION['user']['name'];
} else $member = false;
if(isset($_SESSION['msg'])) {
$message = mysql_real_escape_string($_SESSION['msg']);
unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
if($count%2==0) {
$style = "#EEFFEE";
} else {
$style = "#FFFFFF";
}
$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
$result1 = mysql_query($sql, $conn);
$row1 = mysql_fetch_array($result1);
$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
$count++;
}
$view = Fillano\Core\View::getInstance('views');
$view->assign(array(
'member'=>$member,
'data'=>$data,
'name'=>$name,
'message'=>$message
));
$view->render('index', 'html');
就這樣,大概花了一個多小時,就把之前使用的Twig包裝成一個View類別,設計好統一的Facade介面來操作,如果將來要改用其他的樣板引擎,就照這樣的Facade把它包起來就可以使用,需要改的只有設定檔。
改完以後再測試一下,嗯...雖然中途跌倒幾次,不過總算可以操作,畫面也沒問題。
======
View的部份就先加工到這裡。一路上其實只改了index.php這一支程式,但是只要index.php可以這樣改,其他部分也可以這樣逐步改善。
不過即使把View整個從程式抽離,程式的邏輯還是混在一起,無法做單元測試來確保商業邏輯的正確性。所以,下一步還是先來從目前的程式中把Model抽出來。中途有時間的話,也考慮一下資料庫操作的抽象化。至於Controller...就擺在最後吧。